﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using OpenLS.Drawing;
using OpenLS.Drawing.UI;

namespace OpenLS.Spreadsheet.UI.Internals
{
    class CellSparkLineControl: CellControl
    {
        protected override bool IsRenderProperty(string name)
        {
            switch (name)
            {
                case "Value"
                :
                    return true;
                default:
                    return false;
            }
        }
        ColorScheme _previousScheme;

        protected override void OnRender(DrawingContext drawingContext)
        {
            Theme theme = DrawingElement.GetTheme(this);
            ColorScheme colorScheme = null;
            if (theme != null)
                colorScheme = theme.ColorScheme;
            if (_previousScheme != colorScheme)
            {
                _previousScheme = colorScheme;
            }
            RenderCellSparkLines(drawingContext, colorScheme);
        }

        private void RenderCellSparkLines(DrawingContext drawingContext, ColorScheme colorScheme)
        {
            if (WorksheetPane == null)
                return;
            foreach (var sparklineGroup in WorksheetPane.Worksheet.SparkLineGroups)
                RenderSparkLineGroup(sparklineGroup, drawingContext, colorScheme);
        }

        private void RenderSparkLineGroup(SparkLineGroup sparklineGroup, DrawingContext drawingContext, ColorScheme colorScheme)
        {
            foreach (SparkLine line in Enumerable.Where(sparklineGroup.SparkLines, l => WorksheetPane.Contains(l.Target)))
            {
                RenderSparkLine(sparklineGroup, line, drawingContext, colorScheme);
            }
        }

        private void RenderSparkLine(SparkLineGroup group,  SparkLine line, DrawingContext drawingContext, ColorScheme colorScheme)
        {
            SparkLineRendererBase renderer = SparkLineRendererBase.Create(group, line, colorScheme, GetRelativeRect(line.Target));
            renderer.TryRenderSparkLine(drawingContext);

        }
        abstract class SparkLineRendererBase
        {
            protected readonly SparkLineGroup sparkLineGroup;
            protected readonly SparkLine sparkLine;
            protected readonly ColorScheme colorScheme;
            private readonly Rect rect;

            public SparkLineRendererBase(SparkLineGroup sparkLineGroup, SparkLine sparkLine, ColorScheme colorScheme, Rect rect)
            {
                this.sparkLineGroup = sparkLineGroup;
                this.sparkLine = sparkLine;
                this.colorScheme = colorScheme;
                this.rect = rect;
            }


            internal virtual void TryRenderSparkLine(DrawingContext drawingContext)
            {
                List<object> values = new List<object>(this.sparkLine.GetValues());
                List<double> doubleValues = new List<double>(Enumerable.OfType<double>(values));
                if (doubleValues.Count == 0)
                    return;
                if (rect.Height < 8 || rect.Width < 8)
                    return;
                var actualRect = new Rect(rect.Left + 4, rect.Top + 4, rect.Width - 8, rect.Height - 8);
                RenderSparkLine(Indexed(doubleValues), drawingContext, actualRect, doubleValues.Count);
            }

            internal abstract void RenderSparkLine(IEnumerable<DataItem> values, DrawingContext drawingContext, Rect rect, int count);

            internal static SparkLineRendererBase Create(SparkLineGroup group, SparkLine line, ColorScheme colorScheme, Rect rect)
            {
                switch (group.Type)
                {
                    case SparkLineType.Line:
                        return new LineSparkLineRenderer(group, line, colorScheme, rect);
                    case SparkLineType.Column:
                        return new ColumnSparkLineRenderer(group, line, colorScheme, rect);
                    case SparkLineType.Stacked:
                        return new StackedSparkLineRenderer(group, line, colorScheme, rect);
                    default:
                        throw new NotSupportedException();
                }
            }
        }
        class LineSparkLineRenderer : SparkLineRendererBase
        {
            public LineSparkLineRenderer(SparkLineGroup sparkLineGroup, SparkLine sparkLine, ColorScheme colorScheme, Rect rect) : base(sparkLineGroup, sparkLine, colorScheme, rect)
            {
            }

            internal override void RenderSparkLine(IEnumerable<DataItem> pairs, DrawingContext drawingContext, Rect rect, int count)
            {
                var actualRect = new Rect(rect.Left + 4, rect.Top + 4, rect.Width - 8, rect.Height - 8);
                double max = sparkLineGroup.ManualMaximum != null ? sparkLineGroup.ManualMaximum.Value : Enumerable.Max(Enumerable.Select(pairs, v => v.Y));
                double min = sparkLineGroup.ManualMinimum != null ? sparkLineGroup.ManualMinimum.Value : Enumerable.Min(Enumerable.Select( pairs, v => v.Y));
                double delta = max - min;
                if (delta <= 0)
                    delta = 1;
                Func<DataItem, Point> selector =
                    p =>
                    new Point(rect.Left + (double)p.X * rect.Width / (count - 1),
                              rect.Bottom - ((p.Y - min) / delta * rect.Height));
                var screenValues = Enumerable.Select(pairs, selector);
                IEnumerable<PathSegment> segments = Enumerable.Select(screenValues, p => (PathSegment)new LineSegment(p, true));
                PathFigure pathFigure = new PathFigure(selector(Enumerable.First(pairs)), segments, false);
                PathGeometry lineGeometry = new PathGeometry(new[] { pathFigure });
                drawingContext.DrawGeometry(null, new Pen(new SolidColorBrush(sparkLineGroup.ColorSeries.GetColor(colorScheme)), sparkLineGroup.LineWeight), lineGeometry);

            }
            
        }
        class ColumnSparkLineRenderer : SparkLineRendererBase
        {
            public ColumnSparkLineRenderer(SparkLineGroup sparkLineGroup, SparkLine sparkLine, ColorScheme colorScheme, Rect rect) : base(sparkLineGroup, sparkLine, colorScheme, rect)
            {
            }

            internal override void RenderSparkLine(IEnumerable<DataItem> pairs, DrawingContext drawingContext, Rect rect, int count)
            {

                double max = sparkLineGroup.ManualMaximum != null ? sparkLineGroup.ManualMaximum.Value : Enumerable.Max(Enumerable.Select( pairs, p => p.Y));
                double min = sparkLineGroup.ManualMinimum != null ? sparkLineGroup.ManualMinimum.Value : Enumerable.Min(Enumerable.Select(pairs, p => p.Y));
                double delta = max - min;
                if (delta < 0)
                    delta = 1;
                double itemWidth = rect.Width/count;
                Func<DataItem, Rect> selector2 =
                    p =>
                    new Rect(rect.Left + (double)p.X * itemWidth + itemWidth * 0.1,
                        rect.Bottom - (p.Y - min) / delta * rect.Height,
                        itemWidth * 0.8,
                             (p.Y - min)/delta*rect.Height);
                var f2 = Enumerable.Select(pairs, selector2);
                foreach (var g in f2)
                    drawingContext.DrawRectangle(new SolidColorBrush(this.sparkLineGroup.ColorSeries.GetColor(colorScheme)), null, g);

            }

        }
        class StackedSparkLineRenderer : SparkLineRendererBase
        {
            public StackedSparkLineRenderer(SparkLineGroup sparkLineGroup, SparkLine sparkLine, ColorScheme colorScheme, Rect rect) : base(sparkLineGroup, sparkLine, colorScheme, rect)
            {
            }

            internal override void RenderSparkLine(IEnumerable<DataItem> pairs, DrawingContext drawingContext, Rect rect, int count)
            {
                double itemWidth = rect.Width/count;
                foreach (var point in pairs)
                {
                    if (point.Y > 0)
                        drawingContext.DrawRectangle(new SolidColorBrush(sparkLineGroup.ColorSeries.GetColor(colorScheme)), null, new Rect(
                            rect.Left + point.X * itemWidth + itemWidth / 10, rect.Top, itemWidth * 0.8, rect.Height / 2));
                    else if (point.Y < 0)
                        drawingContext.DrawRectangle(new SolidColorBrush(sparkLineGroup.ColorSeries.GetColor(colorScheme)), null, new Rect(
                            rect.Left + point.X * itemWidth + itemWidth / 10, rect.Top + rect.Height / 2, itemWidth * 0.8, rect.Height / 2));
                    else 
                        drawingContext.DrawRectangle(new SolidColorBrush(sparkLineGroup.ColorSeries.GetColor(colorScheme)), null, new Rect(
                            rect.Left + point.X * itemWidth + itemWidth / 10, rect.Top + rect.Height / 2, itemWidth * 0.8, 1));
                }
            }
        }

        private class DataItem
        {
            public int X;

            public DataItem(int x, double y)
            {
                this.X = x;
                this.Y = y;
            }

            public double Y;
        }
        static IEnumerable<DataItem> Indexed(IEnumerable<double> source)
        {
            int i = 0;
            foreach (var t in source)
                yield return new DataItem(i++, t);
        }
    }
}